/*
 *     Prionace - A file navigator to manage your files
 *
 *     Copyright (C) 2012 Francivan Bezerra
 *
 *     This file is part of Prionace.
 *
 *     Prionace is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     Prionace is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with Prionace.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fba.prionace.file;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.WindowConstants;

import org.fba.prionace.ui.ExceptionHandler;
import org.fba.prionace.ui.viewer.Viewer;

public class FileCopyOrMove {

	private List<File> source;

	private File dest;

	private boolean move;

	private JComponent parentComponent;

	private static JFrame operationFrame;

	public FileCopyOrMove(List<File> source, File dest,
			JComponent parentComponent, boolean move) {
		setSource(source);
		setDest(dest);
		setParentComponent(parentComponent);
		setMove(move);

		if (operationFrame == null) {
			operationFrame = new JFrame("Operação em andamento");
			operationFrame.setResizable(false);
			operationFrame.setLayout(new BoxLayout(operationFrame
					.getContentPane(), BoxLayout.Y_AXIS));
			operationFrame
					.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
		}
	}

	public List<File> getSource() {
		return source;
	}

	public void setSource(List<File> source) {
		this.source = source;
	}

	public File getDest() {
		return dest;
	}

	public void setDest(File dest) {
		this.dest = dest;
	}

	public JComponent getParentComponent() {
		return parentComponent;
	}

	public void setParentComponent(JComponent parentComponent) {
		this.parentComponent = parentComponent;
	}

	public void setMove(boolean move) {
		this.move = move;
	}

	public boolean showConfirmOperationDialog() {
		if (source.isEmpty()) {
			return false;
		}

		JList<String> files;
		Vector<String> names = new Vector<String>();
		JScrollPane listScroller;
		JPanel message = new JPanel(new BorderLayout(0, 10));
		String copyOrMove = move ? "Mover:" : "Copiar:";
		String sourceDir = source.get(0).getParentFile().getAbsolutePath();

		for (File file : source) {
			if (move
					&& (sourceDir.equals(dest.getAbsolutePath()) || file
							.equals(dest))) {
				return false;
			}
			names.add(file.getName());
		}
		files = new JList<String>(names);
		listScroller = new JScrollPane(files);
		listScroller.setPreferredSize(new Dimension(300, 100));

		message.add(new JLabel(copyOrMove), BorderLayout.NORTH);
		message.add(listScroller, BorderLayout.CENTER);
		message.add(new JLabel("Para: " + getDest().getAbsolutePath()),
				BorderLayout.SOUTH);

		return JOptionPane.showConfirmDialog(parentComponent, message,
				"Confirmar operação", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
	}

	public void executeOperation() {
		FileOperation operation = new FileOperation();
		Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();

		operationFrame.setVisible(false);
		operationFrame.add(operation.getStatusPane());
		operationFrame.pack();
		operationFrame.setLocation(
				(screen.width - operationFrame.getSize().width) / 2,
				(screen.height - operationFrame.getSize().height) / 2);
		operationFrame.setVisible(true);

		operation.execute();
	}

	class FileOperation extends SwingWorker<Void, Void> implements
			PropertyChangeListener, ActionListener {
		private JLabel topLabel;

		private JLabel bottomLabel;

		private JProgressBar progressBar;

		private JButton cancelButton;

		private JPanel statusPane;

		private long totalSize;

		private ArrayDeque<File> sourceFiles;

		public FileOperation() {
			init();
		}

		public JPanel getStatusPane() {
			return statusPane;
		}

		private void init() {
			topLabel = new JLabel("Preparando...");
			bottomLabel = new JLabel("Arquivos: 0");
			progressBar = new JProgressBar();
			cancelButton = new JButton("Cancelar");
			statusPane = new JPanel(new BorderLayout());

			topLabel.setHorizontalAlignment(SwingConstants.CENTER);
			bottomLabel.setHorizontalAlignment(SwingConstants.CENTER);
			cancelButton.addActionListener(this);

			JPanel centerPane = new JPanel(new BorderLayout());
			centerPane.add(progressBar, BorderLayout.CENTER);
			centerPane.add(cancelButton, BorderLayout.EAST);

			statusPane.setPreferredSize(new Dimension(430, 60));
			statusPane.add(topLabel, BorderLayout.NORTH);
			statusPane.add(centerPane, BorderLayout.CENTER);
			statusPane.add(bottomLabel, BorderLayout.SOUTH);
			statusPane.setBorder(BorderFactory.createEtchedBorder());

			addPropertyChangeListener(this);

			totalSize = 0;
			sourceFiles = new ArrayDeque<File>();
		}

		private void prepareFiles() {
			progressBar.setIndeterminate(true);
			progressBar.setStringPainted(false);
			for (File file : source) {
				if (file.isFile()) {
					totalSize += file.length();
					sourceFiles.add(file);
					firePropertyChange(null, null, null);
				} else {
					addDir(file);
				}
			}
			progressBar.setIndeterminate(false);
			progressBar.setStringPainted(true);
		}

		private void addDir(File file) {
			if (!file.isDirectory()) {
				return;
			}

			File[] files = file.listFiles();
			for (File f : files) {
				if (f.isDirectory()) {
					addDir(f);
				} else {
					totalSize += f.length();
					sourceFiles.add(f);
					firePropertyChange(null, null, null);
				}
			}
		}

		private void copyFiles() {
			FileChannel from;
			FileChannel to;
			File destFile;
			String action = move ? "Movendo" : "Copiando";
			String sourceDir = source.get(0).getParentFile().getAbsolutePath();
			String[] options = { "Substituir", "Ignorar", "Cancelar" };
			int option;
			int progress;
			int filesCount = 0;
			long fileSize;
			long bytesCopied;
			long totalBytesCopied = 0;

			topLabel.setHorizontalAlignment(SwingConstants.LEADING);

			for (File sourceFile : sourceFiles) {
				option = 0;
				destFile = new File(dest.getAbsolutePath().concat(
						sourceFile.getAbsolutePath().substring(
								sourceDir.length())));

				if (destFile.exists()) {
					FileExistsDialog destExists = new FileExistsDialog(
							sourceFile, destFile);
					option = JOptionPane.showOptionDialog(parentComponent,
							destExists, "Arquivo existente",
							JOptionPane.YES_NO_CANCEL_OPTION,
							JOptionPane.QUESTION_MESSAGE, null, options,
							options[0]);
					if (option == JOptionPane.CANCEL_OPTION) {
						cancel(true);
					}
				}

				if (option != JOptionPane.NO_OPTION && !isCancelled()) {
					try {
						if (!destFile.exists()) {
							destFile.getParentFile().mkdirs();
							destFile.createNewFile();
						}

						from = new FileInputStream(sourceFile).getChannel();
						to = new FileOutputStream(destFile).getChannel();
						bytesCopied = 0;
						fileSize = from.size();

						do {
							topLabel.setText(action.concat(" [")
									.concat(sourceFile.getName())
									.concat("] para [")
									.concat(dest.getAbsolutePath()).concat("]"));
							bottomLabel.setText("Arquivo " + (++filesCount)
									+ " de " + sourceFiles.size());
							totalBytesCopied += fileSize;
							progress = (int) (100 * totalBytesCopied / totalSize);
							progressBar.setValue(progress);
							setProgress(progress);
						} while ((bytesCopied += to.transferFrom(from,
								bytesCopied, fileSize - bytesCopied)) < fileSize);

						from.close();
						to.close();

						destFile.setLastModified(sourceFile.lastModified());

						if (!isCancelled() && move) {
							sourceFile.delete();
						}
					} catch (IOException e) {
						ExceptionHandler.showException(e, parentComponent);
					}
				}
			}
		}

		private void deleteFiles() {
			JFile delete;

			for (File sourceFile : source) {
				if (sourceFile.exists()) {
					delete = new JFile(sourceFile);
					try {
						delete.delete();
					} catch (SecurityException e) {
						ExceptionHandler.showException(e, parentComponent);
					}
				}
			}
		}

		@Override
		protected Void doInBackground() throws Exception {
			prepareFiles();
			copyFiles();
			if (!isCancelled() && move) {
				deleteFiles();
			}
			return null;
		}

		@Override
		protected void done() {
			operationFrame.setVisible(false);
			operationFrame.remove(statusPane);
			if (operationFrame.getContentPane().getComponentCount() > 0) {
				operationFrame.pack();
				operationFrame.setVisible(true);
			}
			((Viewer) parentComponent).refreshFilesList();
		}

		public void propertyChange(PropertyChangeEvent evt) {
			if (evt.getPropertyName() == null) {
				bottomLabel.setText("Arquivos: " + sourceFiles.size());
			}
		}

		public void actionPerformed(ActionEvent e) {
			cancel(true);
			System.out.println("cancelado");
		}
	}
}
